package com.aluka.nirvana.framework.datasource.configuration;

import com.aluka.nirvana.framework.datasource.constant.NirvanaDataSource;
import com.aluka.nirvana.framework.datasource.model.DataSourceWrap;
import com.aluka.nirvana.framework.datasource.util.ReUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.hibernate.jpa.HibernateEntityManager;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationUtils;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

import javax.sql.DataSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * Jpa 数据源初始化配置类
 * @author gongli
 * @since 2019/12/17
 */
@Slf4j
@EnableJpaRepositories
public class JpaConfiguration {

    @Setter
    private JpaProperties jpaProperties;

    private JpaConfiguration(){}

    private static JpaConfiguration jpaConfiguration;

    public static JpaConfiguration getInstance(JpaProperties jpaProperties){
        if(jpaConfiguration == null) {
            jpaConfiguration = new JpaConfiguration();
            jpaConfiguration.setJpaProperties(jpaProperties);
        }
        return jpaConfiguration;
    }

    /**
     * 检查使用Jpa时必要配置是否正确
     * @param dbName 数据源名称
     * @param dataSourceWrap 数据源配置信息
     * @return 正确与否
     */
    public boolean checkJpaConfig(String dbName, DataSourceWrap dataSourceWrap) {
        if(Strings.isEmpty(dataSourceWrap.getEntityPackages())){
            log.warn(">>Nirvana<< 跳过数据源[{}]Jpa相关初始化配置，因为没有配置实体包扫描路径 'nirvana.database.datasource.{}.entityPackages'",dbName,dbName);
            return false;
        }else if(Strings.isEmpty(dataSourceWrap.getRepositoryPackages())){
            log.warn(">>Nirvana<< 跳过数据源[{}]Jpa相关初始化配置，因为没有配置Repository包扫描路径 'nirvana.database.datasource.{}.repositoryPackages'",dbName,dbName);
            return false;
        }
        return true;
    }
    /**
     * 初始化Jpa默认多数据源时EntityManager配置信息
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param v 数据源配置信息
     * @param applicationContext Spring上下文
     * @param isAnnotation 是否注解驱动
     */
    public void initEntityManager(BeanDefinitionRegistry registry, String k, DataSourceWrap v, ApplicationContext applicationContext,boolean isAnnotation) {
        if(registry.isBeanNameInUse(k + NirvanaDataSource.ENTITY_MANAGER)){
            return;
        }
        GenericBeanDefinition jpaEntityManagerBean = new GenericBeanDefinition();
        jpaEntityManagerBean.setBeanClass(JpaEntityManager.class);
        jpaEntityManagerBean.setSynthetic(true);
        jpaEntityManagerBean.setPrimary(false);
        DataSource dataSource;
        if(isAnnotation){
            dataSource = applicationContext.getBean(DataSource.class);
        }else{
            dataSource = applicationContext.getBean(k + NirvanaDataSource.DATA_SOURCE, DataSource.class);
        }
        ConstructorArgumentValues constructorArgumentValues = jpaEntityManagerBean.getConstructorArgumentValues();
        constructorArgumentValues.addIndexedArgumentValue(0, dataSource, DataSource.class.getName());
        constructorArgumentValues.addIndexedArgumentValue(1, v.getEntityPackages(), String.class.getName());
        constructorArgumentValues.addIndexedArgumentValue(2, k,String.class.getName());
        constructorArgumentValues.addIndexedArgumentValue(3, new HibernateJpaVendorAdapter(), JpaVendorAdapter.class.getName());
        constructorArgumentValues.addIndexedArgumentValue(4, jpaProperties, JpaProperties.class.getName());
        registry.registerBeanDefinition(k + NirvanaDataSource.ENTITY_MANAGER, jpaEntityManagerBean);
        log.debug(">>Nirvana<< 初始化数据源[{}] JpaEntityManager[basePackages > {}] 成功",k, v.getEntityPackages());
    }
    /**
     * 初始化Jpa默认多数据源时PlatformTransactionManager配置信息
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param v 数据源配置信息
     * @param applicationContext Spring上下文
     */
    public void initPlatformTransactionManager(BeanDefinitionRegistry registry, String k,DataSourceWrap v, ApplicationContext applicationContext) {
        if(registry.isBeanNameInUse(k + NirvanaDataSource.JPA_PLATFORM_TRANSACTION_MANAGER)){
            return;
        }
        Object entityManager = applicationContext.getBean(k + NirvanaDataSource.ENTITY_MANAGER);
        GenericBeanDefinition platformTransactionManagerBean = new GenericBeanDefinition();
        platformTransactionManagerBean.setBeanClass(JpaTransactionManager.class);
        platformTransactionManagerBean.setSynthetic(true);
        platformTransactionManagerBean.setPrimary(v.getIsPrimary());
        MutablePropertyValues mpv = platformTransactionManagerBean.getPropertyValues();
        mpv.addPropertyValue("entityManagerFactory", entityManager);
        registry.registerBeanDefinition(k + NirvanaDataSource.JPA_PLATFORM_TRANSACTION_MANAGER, platformTransactionManagerBean);
        log.debug(">>Nirvana<< 初始化数据源[{}] JpaTransactionManager[name={}] 成功", k, k + NirvanaDataSource.JPA_PLATFORM_TRANSACTION_MANAGER);
    }
    /**
     * 初始化Jpa默认多数据源 包扫描配置信息 不同包路径下的Repository采用不同的事务管理器
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param v 数据源配置信息
     * @param applicationContext Spring上下文
     */
    public void initRepositoryConfig(BeanDefinitionRegistry registry, String k, DataSourceWrap v, ApplicationContext applicationContext) {
        EnableJpaRepositories annotation = JpaConfiguration.class.getAnnotation(EnableJpaRepositories.class);
        //绑定属性
        InvocationHandler handler = Proxy.getInvocationHandler(annotation);
        Map memberValues = (Map) ReUtils.getFieldValue(handler, "memberValues");
        memberValues.put("basePackages", v.getRepositoryPackages().split(","));
        memberValues.put("entityManagerFactoryRef", k + NirvanaDataSource.ENTITY_MANAGER);
        memberValues.put("transactionManagerRef", k + NirvanaDataSource.JPA_PLATFORM_TRANSACTION_MANAGER);
        StandardAnnotationMetadata annotationMetadata = new StandardAnnotationMetadata(JpaConfiguration.class,true);
        Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(EnableJpaRepositories.class.getName());
        annotationAttributes.put("includeFilters", new AnnotationAttributes[0]);
        annotationAttributes.put("excludeFilters", new AnnotationAttributes[0]);

        AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(
                annotationMetadata, EnableJpaRepositories.class, applicationContext, applicationContext.getEnvironment(), registry);

        RepositoryConfigurationExtension extension = new JpaRepositoryConfigExtension();
        RepositoryConfigurationUtils.exposeRegistration(extension, registry, configurationSource);

        RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, applicationContext,applicationContext.getEnvironment());
        delegate.registerRepositoriesIn(registry, extension);
        log.debug(">>Nirvana<< 初始化数据源[{}] JpaRepositoryConfig ：[basePackages > {}] 成功", k,v.getRepositoryPackages());
    }

    /**
     * 初始化使用注解驱动时Jpa数据源相关配置项
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param v 数据源配置信息
     * @param applicationContext Spring上下文
     */
    public void initAnnotationConfiguration(BeanDefinitionRegistry registry, String k, DataSourceWrap v, ApplicationContext applicationContext){
        initEntityManager(registry,k,v,applicationContext,true);
        initPlatformTransactionManager(registry,k,v,applicationContext);
        initRepositoryConfig(registry,k,v,applicationContext);
    }
}

